package io.gitee.hefren.hhsharding.mybatis;

import com.google.common.base.Strings;
import io.gitee.hefren.hhsharding.core.HhShardingContext;
import io.gitee.hefren.hhsharding.core.HhShardingResult;
import io.gitee.hefren.hhsharding.demo.model.User;
import io.gitee.hefren.hhsharding.engine.HhShardingEngine;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSession;
import org.mybatis.spring.mapper.MapperFactoryBean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.ClassUtils;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.List;
import java.util.Objects;

/**
 * @Date 2024/8/16
 * @Author lifei
 */
public class HhShardingMapperFactoryBean<T> extends MapperFactoryBean<T> {
    private static final Logger log = LoggerFactory.getLogger(HhShardingMapperFactoryBean.class);

    // 添加setter方法，spring会自动注入这个属性（如果这个对象存在）
    private HhShardingEngine hhShardingEngine;

    public HhShardingMapperFactoryBean() {
    }

    public HhShardingMapperFactoryBean(Class<T> mapperInterface) {
        super(mapperInterface);
    }

    @Override
    public T getObject() throws Exception {
        T originProxy = super.getObject();
        Class<T> clazz = getMapperInterface();
        SqlSession sqlSession = getSqlSession();
        Configuration configuration = sqlSession.getConfiguration();
        Object resultProxy = Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, (Object proxy, Method method, Object[] args) -> {
            String methodId = Strings.lenientFormat("%s.%s", clazz.getName(), method.getName());
            MappedStatement mappedStatement = configuration.getMappedStatement(methodId);
            BoundSql boundSql = mappedStatement.getBoundSql(args);
            String originalSQL = boundSql.getSql();
            List<String> propertyList = boundSql.getParameterMappings().stream().map(ParameterMapping::getProperty).toList();
            Object[] parameters = fetchParameters(propertyList, args);
            HhShardingResult hhShardingResult = hhShardingEngine.sharding(originalSQL, parameters);
            HhShardingContext.setHhShardingResult(hhShardingResult);
            return method.invoke(originProxy, args);
        });
        return (T) resultProxy;
    }

    private Object[] fetchParameters(List<String> propertyList, Object[] args) {
        Object[] result = args;
        if (Objects.nonNull(args) && args.length==1 && !ClassUtils.isPrimitiveOrWrapper(args[0].getClass())) {
            Object param = args[0];
            result = propertyList.stream().map(propertyName->fetchObjectFieldValue(param, propertyName))
                    .toArray(Object[]::new);
        }
        return result;
    }

    private Object fetchObjectFieldValue(Object obj, String propertyName) {
        try {
            Field declaredField = obj.getClass().getDeclaredField(propertyName);
            declaredField.setAccessible(true);
            return declaredField.get(obj);
        }catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public HhShardingEngine getHhShardingEngine() {
        return hhShardingEngine;
    }

    public void setHhShardingEngine(HhShardingEngine hhShardingEngine) {
        this.hhShardingEngine = hhShardingEngine;
    }
}
